package cn.xu.tool.big.a.compments.transaction;

import cn.xu.tool.big.a.compments.IndicatorCalculator;
import cn.xu.tool.big.a.compments.strategy.TradingStrategy;
import cn.xu.tool.big.a.dto.KLine;
import cn.xu.tool.big.a.dto.TotalReturn;
import cn.xu.tool.big.a.dto.TradeRecord;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 类名: TransactionTest
 * 描述: TODO
 * 作者: ClarkXu clark2021@qq.com
 * 日期: 2025/7/8
 */
public class TransactionCp4 {

    public static List<TradeRecord> doTransactionBt(List<KLine> kLines, String stockCode, String startDate, String endDate,
                                                    double initialCapital, double commissionRate,
                                                    TotalReturn totalReturn) throws Exception {
        // 3. 生成交易信号
        int[] signals = generateSignals(kLines, 1, 5);

        // 4. 回测
        return backtest(kLines, signals, initialCapital, commissionRate, totalReturn, false, 0.998, 1.01);
    }

    // 双均线策略：短期均线上穿长期均线买入，下穿卖出
    // 返回信号数组：1表示买入，-1表示卖出，0表示无操作
    public static int[] generateSignals(List<KLine> kLines, int shortPeriod, int longPeriod) {

        // 2. 计算指标
        double[] shortMA = IndicatorCalculator.calculateMA(kLines, shortPeriod);  // 5日均线
        double[] longMA = IndicatorCalculator.calculateMA(kLines, longPeriod);   // 20日均线

        int[] signals = new int[shortMA.length];
        // 初始无信号
        for (int i = 0; i < signals.length; i++) {
            signals[i] = 0;
        }

        // 从足够长的位置开始（避免前面有0值）
        int startIndex = 0;
        for (int i = 0; i < shortMA.length; i++) {
            if (shortMA[i] != 0 && longMA[i] != 0) {
                startIndex = i;
                break;
            }
        }

        for (int i = startIndex; i < shortMA.length; i++) {
            // 前一天短均线低于长均线，当天短均线高于长均线 -> 金叉
            if (i > 0 && shortMA[i - 1] < longMA[i - 1] && shortMA[i] > longMA[i]) {
                signals[i] = 1; // 买入信号
                KLine kLine = kLines.get(i);
                System.out.println("金叉 买入信号:【"+kLine.getDate()+"】 收盘价:"+kLine.getClose());
            }
            // 前一天短均线高于长均线，当天短均线低于长均线 -> 死叉
            else if (i > 0 && shortMA[i - 1] > longMA[i - 1] && shortMA[i] < longMA[i]) {
                signals[i] = -1; // 卖出信号
                KLine kLine = kLines.get(i);
                System.out.println("死叉 卖出信号:【"+kLine.getDate()+"】 收盘价:"+kLine.getClose());
            }
        }

        return signals;
    }

    public static List<TradeRecord> backtest(List<KLine> kLines, int[] signals,
                                             double initialCapital, double commissionRate,
                                             TotalReturn totalReturn, boolean allSell,
                                             double buyDiscount, double sellDiscount) {
        double capital = initialCapital;
        int totalPosition = 0;
        double totalCost = 0; // 新增：记录累计持仓成本
        double lastBuyPrice = 0;
        double lastSellPrice = 0; // 新增：记录上一次卖出价格
        boolean isFirstBuy = true; // 新增：标记是否为首次买入
        int tradeCount = 0;
        List<TradeRecord> records = new ArrayList<>();
        System.out.println("日期\t\t\t操作\t价格\t\t成交量\t\t仓位\t\t现金\t\t总资产");
        for (int i = 0; i < kLines.size(); i++) {
            KLine kLine = kLines.get(i);
            int signal = signals[i];
            double price = kLine.getClose();

            // 买入逻辑：增加买入价限制条件
            if (signal == 1 && capital > 0) {
                // 首次买入无限制，后续买入需满足价差条件
                boolean canBuy = isFirstBuy || (price <= lastSellPrice * buyDiscount); // 低于前次卖出价3%

                if (canBuy) {
                    double amount = capital * (1 - commissionRate);
                    int tmpPosition = (int) (amount / price);
                    tmpPosition = tmpPosition - tmpPosition % 100;
                    if (tmpPosition >= 100) {
                        totalPosition += tmpPosition;
                        capital -= tmpPosition * price;
                        lastBuyPrice = price * (1 + commissionRate);
                        tradeCount++;
                        isFirstBuy = false; // 首次买入完成后更新标记
                        System.out.printf("%s\t买入\t%.2f\t%d\t%d\t%.2f\t%.2f\n",
                                kLine.getDate(), price, tmpPosition, totalPosition, capital, capital + totalPosition * price);
                        records.add(TradeRecord.create(kLine.getDate(), "买入", price, totalPosition, capital,
                                capital + totalPosition * price, tmpPosition));
                        totalCost += tmpPosition * price; // 新增：累加持仓成本
                    }
                }
                // 不满足买入条件时记录状态
                else if (!isFirstBuy) {
                    double total = capital + totalPosition * price;
                    records.add(TradeRecord.create(kLine.getDate(), "买入跳过(价差不足)", price,
                            totalPosition, capital, total, 0));
                }
            }
            // 卖出逻辑（保留原有止盈条件）
            else if (signal == -1 && totalPosition > 0) {
                // 计算平均持仓成本（总成本/总股数）
                double averageCost = totalCost / totalPosition;
                double minSellPrice = averageCost * sellDiscount; // 要求高出持仓成本一定比例

                if (price >= minSellPrice) {
                    double value = totalPosition * price;
                    capital += value * (1 - commissionRate);
                    lastSellPrice = price; // 记录本次卖出价格
                    int tmpVolume = totalPosition;
                    totalPosition = 0;
                    tradeCount++;
                    System.out.printf("%s\t卖出\t%.2f\t%d\t%d\t%.2f\t%.2f\n",
                            kLine.getDate(), price, totalPosition, totalPosition, capital, capital);
                    records.add(TradeRecord.create(kLine.getDate(), "卖出", price, totalPosition, capital, capital, tmpVolume));
                    totalCost = 0; // 新增：卖出后重置持仓成本
                } else {
                    double total = capital + totalPosition * price;
                    records.add(TradeRecord.create(kLine.getDate(), "持仓（盈利未达标）", price,
                            totalPosition, capital, total, 0));
                }
            }
            // 无信号时记录持仓
            else if (totalPosition > 0) {
                double total = capital + totalPosition * price;
                records.add(TradeRecord.create(kLine.getDate(), "持仓", price, totalPosition, capital, total, 0));
            }
        }

        boolean doAllSell = false;
        if (totalPosition > 0) {
            KLine last = kLines.get(kLines.size() - 1);
            double price = last.getClose();
            double value = totalPosition * price;
            capital += value * (1 - commissionRate);
            if(allSell){
                // 强制平仓逻辑
                System.out.printf("%s\t强制平仓\t%.2f\t%d\t%d\t%.2f\t%.2f\n",
                        last.getDate(), price, totalPosition, totalPosition, capital, capital);
                records.add(TradeRecord.create(last.getDate(), "强制平仓", price, 0, capital, capital, totalPosition));
                totalPosition = 0;
                totalCost = 0; // 新增：平仓后重置持仓成本
                doAllSell = true;
            }else{
                //预测计算出下次卖出的最低价，不得低于持仓成本乘以sellDiscount，并以此为下一个交易日的收盘价计算当前是否是死叉，是则此价格为最终卖出最低价，不是则以此为下一个交易日的收盘价添加到kLines,继续递归计算得出符合死叉条件且又满足sellDiscount的最低卖出价
                calculateMinSell(kLines, totalCost, totalPosition, signals, sellDiscount, buyDiscount);
            }
        }else{
            //预测计算出下次买入的最高价，不得高于上次卖出价格乘以buyDiscount，并以此为下一个交易日的收盘价计算当前是否是金叉，是则此价格为最终买入最高价，不是则以此为下一个交易日的收盘价添加到kLines,继续递归计算得出符合金叉条件且又满足buyDiscount的最高买入价
            calculateMaxBuy(kLines, lastSellPrice, totalPosition, signals, sellDiscount, buyDiscount);
        }

        // 计算总收益
        double finalReturn = capital - initialCapital;
        double returnRate = finalReturn / initialCapital * 100;
        System.out.println("期初股价: " + kLines.get(0).getClose());
        System.out.println("期末股价: " + kLines.get(kLines.size()-1).getClose());
        System.out.println("初始资金: " + initialCapital);
        if(doAllSell||totalPosition==0){
            System.out.println("最终资金: " + capital);
        }else{
            System.out.println("期末仓位(股): " + totalPosition + " * 收盘价换算 -> 最终资金: " + capital + " 持仓成本: "+(totalCost / totalPosition));
        }
        System.out.println("总收益率: " + String.format("%.2f", returnRate) + "%");
        System.out.println("交易次数: " + tradeCount);
        totalReturn.setInitialCapital(initialCapital);
        totalReturn.setFinalCapital(capital);
        totalReturn.setReturnRate(String.format("%.2f", returnRate) + "%");
        totalReturn.setTradeCount(tradeCount);
        return records;
    }


    // 预测最低卖出价（满足死叉信号且盈利条件）
    private static double calculateMinSell(List<KLine> kLines, double totalCost, int totalPosition,
                                           int[] signals, double sellDiscount, double buyDiscount) {
        if (kLines.isEmpty() || totalPosition <= 0) return 0;

        // 计算当前持仓均价和最低可接受价格
        double avgCost = totalCost / totalPosition;
        double minPrice = avgCost * sellDiscount;  // 盈利要求的最低价格[9]

        // 获取最后5个交易日数据（用于均线计算）
        int lastIndex = kLines.size() - 1;
        double[] prices = new double[5];
        for (int i = 0; i < 5; i++) {
            int idx = Math.max(0, lastIndex - 4 + i);
            prices[i] = kLines.get(idx).getClose();
        }

        // 尝试寻找满足死叉信号的最低价格
        for (int attempt = 0; attempt < 100; attempt++) {
            // 模拟新K线：收盘价逐步降低但不低于成本价
            double simulatedPrice = Math.max(minPrice, kLines.get(lastIndex).getClose() - attempt * 0.5);

            // 更新最近5日价格（滑动窗口）
            System.arraycopy(prices, 1, prices, 0, 4);
            prices[4] = simulatedPrice;

            // 计算模拟均线
            double shortMA = prices[4];  // 短期均线=最新价
            double longMA = Arrays.stream(prices).average().orElse(0);  // 长期均线=5日均值

            // 检查死叉信号：短均线下穿长均线
            if (shortMA < longMA && prices[3] >= Arrays.stream(prices, 0, 4).average().orElse(0)) {
                System.out.println("预测成功! 最低卖出价:" + simulatedPrice +
                        " (要求:" + minPrice + ")");
                return simulatedPrice;
            }
        }
        return minPrice;  // 未找到满足条件的价格，按最低要求卖出
    }

    // 预测最高买入价（满足金叉信号且折扣条件）
    private static double calculateMaxBuy(List<KLine> kLines, double lastSellPrice, int totalPosition,
                                          int[] signals, double sellDiscount, double buyDiscount) {
        if (kLines.isEmpty()) return 0;

        // 计算最高可接受价格（基于前次卖出价折扣）
        double maxPrice = lastSellPrice * buyDiscount;  // 折扣要求的最高价[9](@ref)

        // 获取最后5个交易日数据
        int lastIndex = kLines.size() - 1;
        double[] prices = new double[5];
        for (int i = 0; i < 5; i++) {
            int idx = Math.max(0, lastIndex - 4 + i);
            prices[i] = kLines.get(idx).getClose();
        }

        // 尝试寻找满足金叉信号的最高价格
        for (int attempt = 0; attempt < 100; attempt++) {
            // 模拟新K线：收盘价逐步升高但不超过折扣上限
            double simulatedPrice = Math.min(maxPrice, kLines.get(lastIndex).getClose() + attempt * 0.5);

            // 更新最近5日价格
            System.arraycopy(prices, 1, prices, 0, 4);
            prices[4] = simulatedPrice;

            // 计算模拟均线
            double shortMA = prices[4];  // 短期均线=最新价
            double longMA = Arrays.stream(prices).average().orElse(0);  // 长期均线=5日均值

            // 检查金叉信号：短均线上穿长均线
            if (shortMA > longMA && prices[3] <= Arrays.stream(prices, 0, 4).average().orElse(0)) {
                System.out.println("预测成功! 最高买入价:" + simulatedPrice +
                        " (要求:" + maxPrice + ")");
                return simulatedPrice;
            }
        }
        return maxPrice;  // 未找到满足条件的价格，按最高要求买入
    }
}
